package com.personal.core.delay;

import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 延迟缓存
 * @author cuibo
 *
 */
public class DelayCache<K, V>
{
    
    private Map<K, V> cacheMap = new ConcurrentHashMap<K, V>();
    
    private BlockingQueue<CacheKey<K>> queue = new DelayQueue<CacheKey<K>>();
    
    private Map<K, CacheKey<K>> queueMap = new ConcurrentHashMap<K, CacheKey<K>>();   
    private long expirydate;
    
    private Lock lock = new ReentrantLock();
    
    public DelayCache()
    {
        this(0L);
    }
    
    public DelayCache(long expirydate)
    {
        super();
        this.expirydate = expirydate;
        initClearThread();
    }
    
    public V getCache(K key)
    {
        return cacheMap.get(key);
    }
    
    public V getCache(K key, ValueProducer<? extends V> producer)
    {
        if (!cacheMap.containsKey(key))
        {
            setCache(key, producer.produce());
        }
        return getCache(key);
    }
    
    public V getCache(K key, ValueProducer<? extends V> producer, long expirydate)
    {
        if (!cacheMap.containsKey(key))
        {
            setCache(key, producer.produce(), expirydate);
        }
        return getCache(key);
    }
    
    public void setCache(K key, V value)
    {
        setCache(key, value, expirydate);
    }
    
    public void setCache(K key, V value, long expirydate)
    {
        lock.lock();
        try
        {
            cacheMap.put(key, value);
            // 不包含原来的key，则不需要循环去查找
            if (queueMap.containsKey(key))
            {
                CacheKey<K> found = null;
                for (CacheKey<K> contextKey : queue)
                {
                    if (contextKey.getKey().equals(key))
                    {
                        found = contextKey;
                        break;
                    }
                }
                if (found != null)
                {
                    queue.remove(found);
                    queueMap.remove(key);
                }
            }
            CacheKey<K> cacheKey = new CacheKey<K>(key, System.currentTimeMillis(), expirydate);
            if (queue.add(cacheKey))
            {
                queueMap.put(key, cacheKey);
            }
        } finally 
        {
            lock.unlock();
        }
    }
    
    public Object removeCache(K key)
    {
        return cacheMap.remove(key);
    }
    
    public Enumeration<K> getCacheKeys()
    {
        return Collections.enumeration(cacheMap.keySet());
    }
    
    public CreateAndExpiry getCreateAndExpiry(K key)
    {
        return queueMap.get(key);
    }
    
    public void clear()
    {
        this.cacheMap.clear();
        this.queue.clear();
        this.queueMap.clear();
    }
    
    public void shutDown()
    {
        this.cacheMap = null;
        this.queue = null;
        this.queueMap = null;
    }
    
    /**
     * 初始化清理线程
     */
    private void initClearThread()
    {
        Thread local = new Thread(){
            @Override
            public void run()
            {
                while (true)
                {
                    CacheKey<K> key = null;
                    try
                    {
                        key = queue.take();
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    lock.lock();
                    try
                    {
                        if (key != null)
                        {
                            removeCache(key.key);
                            queueMap.remove(key.key);
                        } 
                    } finally 
                    {
                        lock.unlock();
                    }
                }
            }
        };
        local.setDaemon(true);
        local.start();
    }
    
    /**
     * 缓存key
     * @author cuibo
     *
     * @param <K>
     */
    private static class CacheKey<K> extends CreateAndExpiry
    {
        
        public CacheKey(K key, long createDate, long expirydate)
        {
            super(createDate, expirydate);
            this.key = key;
        }
        
        private K key;

        public K getKey()
        {
            return key;
        }
        
    }

}
